#ifndef _tenacitas_resources__pool__h__
#define _tenacitas_resources__pool__h__


// =============================================================================
/**
   @file 
   File description
*/


// =============================================================================
//                                 C++ Headers

#include <memory>
#include <utility>
#include <vector>
#include <cstdint>

// =============================================================================
//                                 3rds Headers

#include <boost/thread/mutex.hpp>

// =============================================================================
//                                 Our Headers


// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants

/**
 */


// =============================================================================
//                                 Macro Commands

/**
 */


/**
   namespace of the organization
*/
namespace tenacitas {
    /**
       namespace for the project
    */
    namespace resources {

        // =========================== Typedefs ===========================
        /** */


        // ============================ Pre-Declarations ==================


        // ============================ Attributes ========================
        /** */


        // ============================ Functions =========================
        /** */


        // ============================ Classes ===========================

        /** A pool of resources

            This pool assumes nothing about the resource, but it demands that
            certain rules are followed. So, please, read the documenation below.

            @tparam t_resource_factory must implement:
            - <tt>typedef ptr;/tt> is the type of the pointer to the factory.
            Please, keep in mind that @p pool will not deallocate the factory
            reference supplied.

            - <tt>typedef resource</tt> is the type of resource that the
            factory creates. Please, read below to be aware what @p resource must
            implement.

            - <tt>std::shared_ptr<resource> initial_creation( );</tt>
            This method should return as many <tt>resource</tt> references as 
            @p t_resource_factory thinks it is worth creating as soon as a
            @p pool object is created.
            In other words, @p pool will call this method until it returns
            a <tt>std::shared_ptr<resource>( )</tt>.
            The idea is to allow @p t_resource_factory to create in advance
            a certain amount of resources it thinks it will be more often used.

            - <tt>std::shared_ptr<resource_r> create( const resource::filter
            &);</tt> This method will be called when @p pool can not find an
            available @p resource that have a certain @p resource::filter,
            and needs to ask to @p t_resource_factory to create more. 


            @tparam t_resource must implement:

            - <tt>typedef filter</tt> this is the type of the data used to
            separate resource objects. For example, if resource is some type of
            buffer, @p filter could be the size of the buffer. @p pool will
            separate buffer objects according to theirs sizes, in order to speed
            up the retrieval.

            - <tt>const filter & filter_value( )</tt> this is a method that
            retrives the value of the filter for a resource.

            @nosubgrouping
        */
        template <typename t_resource_factory>
        class pool {

        public:

            // --- pool - Public Internal Types ------------------------

            /** Type of pointer to a @p pool object */
            typedef std::shared_ptr<pool> ptr;

            /** Type for the resource factory */
            typedef t_resource_factory resource_factory;

            /** Type for the resource factory pointer */
            typedef typename resource_factory::ptr resource_factory_ptr;
            
            /** Type for the resource defined by the resource factory */
            typedef typename resource_factory::resource resource;

            /** Type for the filter defined by the resource */
            typedef typename resource::filter filter;

            /** Pointer to the resource */
            typedef std::shared_ptr<resource> resource_ptr;

            // --- pool - Public Constructors --------------------------

            /** Constructor

                @param [in] p_resource_factory_ptr is a reference to the
                resource factory
            */
            pool( resource_factory_ptr p_resource_factory_ptr );

            /** Destructor */
            ~pool ( ) ;

            // --- pool - Public Accessors -----------------------------

            /** Retrieves an available resource based on a @p filter value

                @param [in] p_filter is the value of the filter that
                a resource must also have

                @return a resource reference, if a resource matching the
                filter was found, <b>or an invalid reference otherwise
            */
            resource_ptr get( const filter & p_filter );

            /** Informs if there are still resources being used */
            bool busy( ) const;
            

        private:

            // --- pool - Private Internal Types -----------------------

            /** List of references to resources */
            typedef std::list<resource_ptr> resources;

            /** A filter and the resources associated */
            struct resources_by_filter
            {
                resources_by_filter( const filter & p_filter )
                    : m_filter( p_filter ),
                      m_resources( ) {}

                filter m_filter;
                resources m_resources;
            };

            /** resources grouped by a filter value */
            typedef std::vector<resources_by_filter> resources_map;

            // --- pool - Private Constructors -------------------------

            /** not allowed */
            pool( );

            /** not allowed */
            pool( const pool & );

            // --- pool - Private Processors ---------------------------

            /** Tries to find the resources list that holds resources that
                have a certain filter value
           
                @param [in] p_resources_map is the map that relates filters to
                a list of resource references

                @param [in] p_filter is the value of the filter

                @return a valid reference to a @p resources object, or
                <b><p>nullptr if there is no @p resources that holds @p resource_ptr
                that have @p p_filter as @p filter value
            */
            resources * find_resources( resources_map & p_resources_map,
                                        const filter & p_filter ) ;

            /** Adding a association of a @p filter to a list of @p resource_ptr
           
                @param [in] p_resources_map is the map that relates filters to
                a list of resource references

                @param [in] p_filter is the value of the filter

                @return a valid reference to the @p resources object created, or
                <b><p>nullptr if it was not possible to create it
            */
            resources * add( resources_map & p_resources_map,
                             const filter & p_filter );

            /** Tries to find an available resource
           
                @param [in] p_resources is the list of a @p resource_ptr that
                have a certain @p filter value

                @return a valid @p resource_ptr object, or <tt>resource_ptr( )</tt>
                if no available @p resource_ptr could be found
            */
            resource_ptr find( resources * p_resources ) ;

            /** Tries to find an available resource
           
                @param [in] p_resources_map is the map that relates filters to
                a list of resource references

                @param [in] p_filter is the value of the filter

                @return a valid @p resource_ptr object, or <tt>resource_ptr( )</tt>
                if no available @p resource_ptr could be found
            */
            resource_ptr find_resource( resources_map & p_resources_map,
                                        const filter & p_filter );

            /** Adds a resource reference to a list of resources
           
                @param [in] p_resources is the list of a @p resource_ptr that
                have a certain @p filter value
           
                @param [in] p_resource_ptr is the resource to be added to the
                @p p_resources list
            */
            void add( resources * p_resources, resource_ptr p_resource_ptr );

            // --- pool - Private Operators ----------------------------

            /** not allowed */
            pool & operator=( const pool & );

            // --- pool - Private Attributes ---------------------------

            /** resource factory */
            resource_factory_ptr m_resource_factory;

            /** Relates filters to a list of resource references*/
            resources_map m_resources_map;

            /** To protect us */
            boost::mutex m_mutex;
        };
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::resources;

// ----------------------------------------------------------------------------
template <typename t_resource_factory>
pool<t_resource_factory>::
pool( resource_factory_ptr p_resource_factory_ptr )
    : m_resource_factory( p_resource_factory_ptr ),

      m_resources_map( ) {
    // we keep on asking m_resource_factory for resources, until it
    // returns resource_ptr( ) 
    while ( true ) {
        resource_ptr l_resource_ptr = m_resource_factory-> initial_creation( ); 
        if ( !l_resource_ptr ) {
            // enough is enough!
            break;
        }

        // find in which resources this resource should be inserted, according
        // to its filterValue
        const filter & l_filter = l_resource_ptr->filter_value( );
        resources * l_resources = find_resources( m_resources_map, l_filter );

        if ( !l_resources ) {
            // this is the first resource associated to this filter value

            // so we create the association list
            l_resources = add( m_resources_map, l_filter );
        }

        // adding the new resource to the resources list
        add( l_resources, l_resource_ptr );
    }

}


// ----------------------------------------------------------------------------
template <typename t_resource_factory>
inline pool<t_resource_factory>::
~pool ( ) { }

// ----------------------------------------------------------------------------
template <typename t_resource_factory>
typename pool<t_resource_factory>::resource_ptr
pool<t_resource_factory>::
get( const filter & p_filter ) {
    return find_resource( m_resources_map, p_filter );
}

// ----------------------------------------------------------------------------
template <typename t_resource_factory>
bool
pool<t_resource_factory>::
busy( ) const {

    resources_map::const_iterator l_map_end = m_resources_map.end( );
    
    for ( resources_map::const_iterator l_map_ite = m_resources_map.begin( );
          l_map_ite != l_map_end;
          ++l_map_ite ) {

        const resources * l_resources = & l_map_ite->resources;

        resources::const_iterator l_resources_end = l_resources->end( );

        for ( resources::const_iterator l_resources_ite = l_resources->begin( );
              l_resources_ite != l_resources_end;
              ++l_resources_ite ) {
            const resource_ptr l_resource = *l_resources_ite;
            
            if ( l_resources.use_count( ) > 1 ) {
                return true;
            }
        }
    }
    return false;
}



// ----------------------------------------------------------------------------
template <typename t_resource_factory>
typename pool<t_resource_factory>::resources *
pool<t_resource_factory>::
find_resources( resources_map & p_resources_map,
                const filter & p_filter ) {
    for ( resources_map::iterator l_map_ite = p_resources_map.begin( );
          l_map_ite != p_resources_map.end( );
          ++l_map_ite ) {
        resources_by_filter * l_resources_by_filter = & ( *l_map_ite );

        if ( l_resources_by_filter->filter == p_filter ) {
            return ( & ( l_resources_by_filter->resources ) );
        }
    }

    return nullptr;
}

// ----------------------------------------------------------------------------
template <typename t_resource_factory>
typename pool<t_resource_factory>::resources *
pool<t_resource_factory>::
add( resources_map & p_resources_map, const filter & p_filter ) {
    p_resources_map.push_back( resources_by_filter ( p_filter ) );

    resources_map::iterator l_ite = p_resources_map.end( );
    --l_ite;

    resources * lmp = & ( ( *l_ite ).resources );

    return lmp;
}

// ----------------------------------------------------------------------------
template <typename t_resource_factory>
typename pool<t_resource_factory>::resource_ptr
pool<t_resource_factory>::
find( resources * p_resources ) {
    for ( resources::const_iterator l_ite = p_resources->begin( );
          l_ite != p_resources->end( );
          ++l_ite ) {
        if ( ( *l_ite ).use_count( ) == 1 ) {
            // if there is only one object using this resource, it means
            // that only the resources where it is inserted is referencing it;
            // hence, it is available, and we can return it
            boost::lock_guard<boost::mutex> l_lock (m_mutex );
            resource_ptr lmp = ( *l_ite );
            return ( lmp );
        }
    }

    // no resource available

    return resource_ptr( );
}

// ----------------------------------------------------------------------------
template <typename t_resource_factory>
inline void
pool<t_resource_factory>::
add( resources * p_resources, resource_ptr p_resource_ptr ) {
    p_resources->push_back( p_resource_ptr );
}


// ----------------------------------------------------------------------------
template <typename t_resource_factory>
typename pool<t_resource_factory>::resource_ptr
pool<t_resource_factory>::
find_resource( resources_map & p_resources_map,
               const filter & p_filter ) {
    resource_ptr l_resource_ptr;

    resources * l_resources = find_resources( p_resources_map, p_filter );
    if ( !l_resources ) {
        // no association list between this resource and this filter was
        // yet created

        // so, we must first ask the factory to create a new resource
        l_resource_ptr = m_resource_factory->create_additional( p_filter );

        if ( l_resource_ptr ) {
            // resource factory created a new resource

            // now we create the association list
            l_resources = add( p_resources_map, p_filter );

            if ( !l_resources ) {
                // ouch!
                l_resource_ptr = resource_ptr( );
            }
            else {
                // and add the resource to the association list
                add( l_resources, l_resource_ptr );
            }
        }
    }
    else {
        // there already is an association between this resource and this filter,
        // so we return the available resource, if any
        l_resource_ptr = find( l_resources );

        if ( !l_resource_ptr ) {
            // poor us, there was no available... 8(

            // so we have to ask the resource factory to create a new resource
            l_resource_ptr = m_resource_factory->Create( p_filter );

            if ( l_resource_ptr ) {
                // resource factory created a new resource

                // add the resource to the association list
                add( l_resources, l_resource_ptr );
            }
        }
    }
   
    return l_resource_ptr;

}
#endif
